;(function($){

    $.expr[':'].linkingToImage = function(elem, index, match){
        // This will return true if the specified attribute contains a valid link to an image:
        return !! ($(elem).attr(match[3]) && $(elem).attr(match[3]).match(/\.(gif|jpe?g|png|bmp)$/i));
    };

    $.fn.previewImage = function(userDefinedSettings){

        var defaults = $.extend({

                /* DEFAULTS */

                // CSS to be applied to image:
                imgCSS: {
                    "max-width":400,
                    "height":"auto",
                    "border-radius":5,
                    "-webkit-box-shadow":"5px 5px 5px 5px hsla(0,0%,5%,1.00)",
                    "box-shadow":"5px 5px 5px 0px hsla(0,0%,5%,0.3)",
                },
                // Distance between cursor and preview:
                distanceFromCursor: {top:10, left:10},
                // Boolean, whether or not to preload images:
                preloadImages: true,
                // Callback: run when link is hovered: container is shown:
                onShow: function(){},
                // Callback: container is hidden:
                onHide: function(){},
                // Callback: Run when image within container has loaded:
                onLoad: function(){},
                // ID to give to container (for CSS styling):
                containerID: 'previewImageContainer',
                // Class to be given to container while image is loading:
                containerLoadingClass: 'loading',
                // Prefix (if using thumbnails), e.g. 'thumb_'
                thumbPrefix: '',
                // Where to retrieve the image from:
                srcAttr: '',
                srcData: '',

            }, userDefinedSettings);

        defaults.imgCSS = $.extend(defaults.imgCSS, userDefinedSettings && userDefinedSettings.imgCSS);
        var options = $.extend(defaults, userDefinedSettings);

        var $imgPreviewContainer = $('<div/>').attr('id', options.containerID)
                .append('<img/>').hide()
                .css('position','absolute')
                .css('z-index', 99999)
                .appendTo('body'),

            $imgView = $('img', $imgPreviewContainer).css(options.imgCSS),

            // Get all valid elements (linking to images / ATTR with image link):
            $imgCollection = options.srcAttr ? this.filter(':linkingToImage(' + options.srcAttr + ')') : this;

        // Re-usable means to add prefix (from setting):
        function addPrefix(src) {
            src = src || '';
            return src.replace(/(\/?)([^\/]+)$/,'$1' + options.thumbPrefix + '$2');
        }

        if (options.preloadImages && options.srcAttr) {
            (function(i){
                var tempIMG = new Image(),
                    callee = arguments.callee;
                tempIMG.src = addPrefix($($imgCollection[i]).attr(options.srcAttr));
                tempIMG.onload = function(){
                    $imgCollection[i + 1] && callee(i + 1);
                };
            })(0);
        }

        $imgCollection
            .mousemove(function(e){

                // $imgPreviewContainer.css({
                //     top: e.pageY + options.distanceFromCursor.top + 'px',
                //     left: e.pageX + options.distanceFromCursor.left + 'px'
                // });

                var wH = document.documentElement.clientHeight;
                var wW = document.documentElement.clientWidth;
                var imgW = $imgView.width();
                var imgH = $imgView.height();
                var cssArr = {"top":"","left":"","bottom":"","right":""};

                if(e.clientX+imgW > wW){
                    if(wW - e.clientX < imgW){
                        cssArr.left = (e.clientX-imgW-10)+"px";

                    }else{
                        cssArr.right = 0;
                    }

                }else{
                    cssArr.left = (e.clientX+10)+"px";
                }

                if(e.clientY+imgH > wH){
                    cssArr.bottom = 0;
                }else{
                    cssArr.top = (e.clientY+10)+"px";
                }
                $imgPreviewContainer.css(cssArr).fadeIn("fast");

            })
            .hover(function(){

                var link = this;
                var _this = $(this);
                var tag = _this.prop('nodeName').toLowerCase(),
                    isImg = (tag === 'img');
                var previewUrl = '';
                previewUrl = previewUrl || (options.srcAttr ? _this.attr(options.srcAttr) : '');
                previewUrl = previewUrl || (options.srcData ? _this.data(options.srcData) : '');
                previewUrl = previewUrl || ( _this.data('preview-uri') || _this.data('uri') || _this.data('href') || (isImg ? _this.attr('src') : _this.attr('href')) );

                $imgPreviewContainer
                    .addClass(options.containerLoadingClass)
                    .show();
                $imgView
                    .load(function(){
                        $imgPreviewContainer.removeClass(options.containerLoadingClass);
                        $imgView.show();
                        options.onLoad.call($imgView[0], link);
                    })
                    .attr( 'src' , addPrefix(previewUrl) );
                options.onShow.call($imgPreviewContainer[0], link);

            }, function(){

                $imgPreviewContainer.hide();
                $imgView.unbind('load').attr('src','').hide();
                options.onHide.call($imgPreviewContainer[0], this);

            });

        // Return full selection, not $imgCollection!
        return this;

    };

})(jQuery);